package xutil

import (
	"fmt"
	"sync"
	"time"
)

type Snow struct {
	nowtime int64
	num     int64
	v长时间    int64
	v长数量    int64
	ul      sync.Mutex
}

var (
	Sid *Snow
	//S完整id *Snow
)

const (
	机器偏移数  = 1
	id偏移数  = 9
	时间戳偏移数 = 机器偏移数 + id偏移数
	当前机器id = 0
	最大机器数  = -1 ^ (-1 << 机器偏移数)
	//当前用的是10毫秒1e7，就是每10毫秒1024个id，每秒大概10万id
	最大id数  = 1<<id偏移数 - 1
	最大id数1 = -1 ^ (-1 << id偏移数)
)
const (
	完整id偏移数  = 10
	完整时间戳偏移数 = 机器偏移数 + 完整id偏移数
	完整最大机器数  = -1 ^ (-1 << 机器偏移数)
	//当前用的是10毫秒1e7，就是每10毫秒1024个id，每秒大概10万id
	完整最大id数  = 1<<完整id偏移数 - 1
	完整最大id数1 = -1 ^ (-1 << 完整id偏移数)
)

func init() {
	Sid = &Snow{
		num:     0,
		nowtime: F获取时间(),
		v长数量:    0,
		v长时间:    F获取时间(1),
	}
}

func F新建Id结构() (s *Snow) {
	return &Snow{
		num:     0,
		nowtime: F获取时间(),
		v长数量:    0,
		v长时间:    F获取时间(1),
	}
}

func (a *Snow) F获取完整雪花id(i int64) (is []int64) {
	a.ul.Lock()
	defer a.ul.Unlock()
	//fmt.Println("打印一下时间戳", Sid)
	//先获取时间戳，判断时间只能大于或者等于上一次时间，如果小于就等待
	当前时间 := F获取时间(1)
	if a.v长时间 < 当前时间 {
		a.v长时间 = 当前时间
		a.v长数量 = 0
	} else if a.v长时间 == 当前时间 {
		a.v长数量++
		if a.v长数量 > 完整最大id数 {
			for a.v长时间 < F获取时间(1) {
				a.v长时间 = F获取时间(1)
			}
		}
	} else { //如果时间大于当前时间，那么时间回拔了，可以直接时间+1，实际上当作等于当前时间好了
		for a.v长时间 < F获取时间() {
			a.v长时间 = F获取时间()
			a.v长数量 = i
		}
	}
	i1 := ((a.v长时间) << 完整时间戳偏移数) | (当前机器id << 机器偏移数) | a.v长数量
	is = []int64{i1}
	for o := 1; o < int(i); o++ {
		is = append(is, i1+int64(o))
	}
	return
}

func (a *Snow) F获取字符串id() string {
	return fmt.Sprintf("%d", a.F获取id())
}

func (a *Snow) F获取id() int64 {
	a.ul.Lock()
	defer a.ul.Unlock()
	//fmt.Println("打印一下时间戳", Sid)
	//先获取时间戳，判断时间只能大于或者等于上一次时间，如果小于就等待
	当前时间 := F获取时间()
	if a.nowtime < 当前时间 {
		a.nowtime = 当前时间
		a.num = 0
	} else if a.nowtime == 当前时间 {
		a.num++
		if a.num > 最大id数 {
			for a.nowtime < F获取时间() {
				a.nowtime = F获取时间()
			}
		}
	} else { //如果时间大于当前时间，那么时间回拔了，可以直接时间+1，实际上当作等于当前时间好了
		for a.nowtime < F获取时间() {
			a.nowtime = F获取时间()
			a.num = 0
		}
	}
	return (a.nowtime)<<时间戳偏移数 | 当前机器id<<机器偏移数 | a.num
}

func F获取时间(i ...int) int64 { //1e6为毫秒,1e7为10毫秒
	if len(i) == 0 {
		return time.Now().UnixNano() / 1e7
	}
	return time.Now().UnixNano() / 1e6
}
